﻿using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using KcpTransport.LowLevel;
using static KcpTransport.LowLevel.KcpMethods;

namespace Hsenl.Network {
    public unsafe class KcpPacketReceiverSender : APacketHandler, IPacketReceiver, IPacketSender, IMessageWriter, IMessageReader, IKcpHandler {
        private const int MTU = 1400;
        private const int MsgHead = 1;
        private const int BodySizeBits = 2;
        private const int TotalHeadBits = 2;

        public event Action<Memory<byte>> OnMessageWritedEvent {
            add => this._packetWriter.OnMessageWritedEvent += value;
            remove => this._packetWriter.OnMessageWritedEvent -= value;
        }

        public event Action<Memory<byte>> OnMessageReadedEvent {
            add => this._packetReader.OnMessageReadedEvent += value;
            remove => this._packetReader.OnMessageReadedEvent -= value;
        }

        private KcpChannel _channel;
        private UdpSocket _socket;
        private IKCPCB* kcp;

        private readonly HBuffer _bufferRecving = new();
        private HBuffer _bufferSending = new();
        private HBuffer _bufferWriting = new();
        private readonly object _bufferLocker = new();
        private readonly ConcurrentQueue<int> _sendingPositions = new();

        private readonly PacketWriter _packetWriter = new();
        private readonly ReaderWriterLockSlim _writerSlim = new();
        private readonly PacketReader _packetReader = new();

        private readonly object _kcpLocker = new();
        private readonly object _readLocker = new();

        protected override void OnInit(Channel channel) {
            this._channel = (KcpChannel)channel;
            this._socket = (UdpSocket)channel.UserToken;

            this._bufferSending.Advance(MsgHead);
            this._bufferWriting.Advance(MsgHead);
            this._bufferSending.Origin = MsgHead;
            this._bufferWriting.Origin = MsgHead;

            this._packetReader.Init(BodySizeBits, TotalHeadBits, true);
            this._packetWriter.Init(BodySizeBits, TotalHeadBits, true); // kcp 在不开启流控的情况下, 是不保证消息顺序的, 所以, 我们要确保自己的消息不被 writer 截断
            this._packetWriter.OutputEvent += () => {
                while (true) {
                    var memory = this._packetWriter.GetSendBuffer(MTU);
                    if (memory.Length == 0)
                        break;

                    this.kcp_send(memory.Span);
                }

                this.kcp_update();
            };
        }

        void IKcpHandler.InitKcp(uint conv) {
            this.kcp = ikcp_create(conv, GCHandle.ToIntPtr(GCHandle.Alloc(this)).ToPointer());
            ikcp_nodelay(this.kcp, 1, 10, 2, 1);
            ikcp_wndsize(this.kcp, 1024, 1024);
            ikcp_setmtu(this.kcp, MTU);
            this.kcp->rx_minrto = 30;
            this.kcp->output = &UnsafeOutput;
        }

        private static int UnsafeOutput(byte* buf, int len, IKCPCB* kcp, void* user) {
            var self = (KcpPacketReceiverSender)GCHandle.FromIntPtr((IntPtr)user).Target!;
            var buffer = new Span<byte>(buf, len);
            self.Output(buffer, len);
            return 0;
        }

        private void Output(Span<byte> data, int len) {
            if (this.IsDisposed)
                return;

            lock (this._bufferLocker) {
                this._bufferWriting.Write(data);
                this._sendingPositions.Enqueue(this._bufferWriting.Position);
            }

            this._socket.SendToAsync();
        }

        byte[] IPacketSender.GetSendBuffer(int length, out int offset, out int count) {
            if (this.IsDisposed) {
                offset = 0;
                count = 0;
                return Array.Empty<byte>();
            }

            length -= MsgHead;
            var len = this._bufferSending.Position;

            // len == 0, 说明没有数据要发了
            if (len == 0) {
                lock (this._bufferLocker) {
                    if (this._bufferWriting.Position != 0) {
                        ObjectHelper.Swap(ref this._bufferSending, ref this._bufferWriting);
                        len = this._bufferSending.Position;
                    }
                    else {
                        offset = 0;
                        count = 0;
                        return null;
                    }
                }
            }

            if (len > length) {
                len = length;
            }

            var origin = this._bufferSending.Origin - MsgHead;
            var tutallen = this._bufferSending.Position + origin;
            var last = false;
            while (true) {
                if (!this._sendingPositions.TryPeek(out var pos))
                    break;

                if (pos == tutallen) {
                    last = true;
                }

                pos -= origin;

                if (pos <= length) {
                    this._sendingPositions.TryDequeue(out _);
                    len = pos;

                    if (last)
                        break;
                }
                else {
                    break;
                }
            }

            offset = origin;
            count = len + MsgHead;
            var buffer = this._bufferSending.GetBuffer();
            buffer.WriteTo(offset, KcpProtocalType.MSG);
            this._bufferSending.Origin += len;

            if (this._bufferSending.Position <= 0) {
                // 所有数据都发送完了, 重置buffer
                this._bufferSending.Origin = 1;
                this._bufferSending.Seek(0, SeekOrigin.Begin);
            }

            if (this.IsDisposed)
                return null;

            return buffer;
        }

        Memory<byte> IPacketSender.GetSendBuffer(int length) {
            if (this.IsDisposed) {
                return default;
            }

            var bytes = ((IPacketSender)this).GetSendBuffer(length, out var offset, out var len);
            return bytes.AsMemory(offset, len);
        }

        byte[] IPacketReceiver.GetRecvBuffer(int len, out int offset, out int count) {
            if (this.IsDisposed) {
                offset = 0;
                count = 0;
                return Array.Empty<byte>();
            }

            this._bufferRecving.GetMemory(len);
            offset = this._bufferRecving.Origin;
            count = len;
            return this._bufferRecving.GetBuffer();
        }

        Memory<byte> IPacketReceiver.GetRecvBuffer(int length) {
            if (this.IsDisposed) {
                return default;
            }

            return this._bufferRecving.GetMemory(length);
        }

        void IMessageWriter.Write(byte[] data, int offset, int count) {
            this.Write(data.AsSpan(offset, count));
        }

        public void Write(Span<byte> data) {
            if (data.Length > MTU - TotalHeadBits) {
                throw new Exception($"msg is too big! {data.Length}");
            }

            if (this.IsDisposed) {
                return;
            }

            var len = data.Length;
            if (len == 0)
                return;

            this.writer_write(data);
        }

        void IMessageReader.Read(Memory<byte> data) {
            if (this.IsDisposed) {
                return;
            }

            lock (this._readLocker) {
                this.kcp_input_recv(data.Slice(1).Span);
            }
        }

        public uint KcpUpdate(uint currentTimeMS) {
            // this.writer_update();
            this.kcp_update();
            return 0;
        }

        public void FlushWriter() {
            this.writer_update();
        }

        private void writer_write(Span<byte> data) {
            this._writerSlim.EnterWriteLock();
            try {
                this._packetWriter.Write(data, this._channel.Service.AutoSend);
            }
            finally {
                this._writerSlim.ExitWriteLock();
            }
        }

        private void writer_update() {
            this._writerSlim.EnterWriteLock();
            try {
                this._packetWriter.Update();
            }
            finally {
                this._writerSlim.ExitWriteLock();
            }
        }

        private void kcp_update() {
            lock (this._kcpLocker) {
                if (this.IsDisposed) {
                    return;
                }

                ikcp_update(this.kcp, TimeInfo.CurrentUtcMilliseconds);
                ikcp_flush(this.kcp);
            }
        }

        private void kcp_send(Span<byte> span) {
            lock (this._kcpLocker) {
                fixed (byte* p = span) {
                    ikcp_send(this.kcp, p, span.Length);
                }
            }
        }

        private void kcp_input_recv(Span<byte> span) {
            int ret;
            int len;
            lock (this._kcpLocker) {
                fixed (byte* p = span) {
                    ret = ikcp_input(this.kcp, p, span.Length);
                }

                len = ikcp_peeksize(this.kcp);
            }

            switch (ret) {
                case 0: {
                    while (len > 0) {
                        var memory = this._packetReader.GetRecvBuffer(len);
                        int recvlen;
                        lock (this._kcpLocker) {
                            fixed (byte* mp = memory.Span) {
                                recvlen = ikcp_recv(this.kcp, mp, len);
                            }
                        }

                        if (recvlen > 0) {
                            this._packetReader.Read(memory.Slice(0, recvlen));
                            if (this._packetReader.Position != 0) {
                                // 交给 kcp 的包里都是完整的消息, 所以每次读都会全部读完, 不会出现有待继续拆的包, 如果出现, 一定是出现了bug 或 kcp 收到的包出现残缺
                                Log.Error("kcp_input_recv reader error!");
                                this._packetReader.Reset();
                            }
                        }

                        lock (this._kcpLocker) {
                            len = ikcp_peeksize(this.kcp);
                        }
                    }

                    break;
                }
                case -1:
                    Log.Error("kcp_input_recv failed!");
                    break;
                case -2:
                    this.kcp_update();
                    break;
            }
        }

        public bool IsAlive() {
            if (this.IsDisposed)
                return false;

            lock (this._kcpLocker) {
                if (this.kcp->state == unchecked((uint)(-1))) {
                    return false;
                }
            }

            return true;
        }

        protected override void OnDisposed() {
            GC.SuppressFinalize(this);
            this._channel = null;
            this._socket = null;
            lock (this._kcpLocker) {
                GCHandle.FromIntPtr((nint)this.kcp->user).Free();
                ikcp_release(this.kcp);
                this.kcp = null;
            }

            this._bufferRecving.Reset();
            this._bufferSending.Reset();
            this._bufferWriting.Reset();
            this._sendingPositions.Clear();

            this._writerSlim.EnterWriteLock();
            try {
                this._packetWriter.Dispose();
            }
            finally {
                this._writerSlim.ExitWriteLock();
            }

            this._packetReader.Dispose();
        }

        ~KcpPacketReceiverSender() {
            if (this.IsDisposed)
                return;

            lock (this._kcpLocker) {
                GCHandle.FromIntPtr((nint)this.kcp->user).Free();
                ikcp_release(this.kcp);
            }
        }
    }
}